﻿using System;
using System.Collections.Generic;
using System.Linq;
using DDDSample.Domain.HandlingEvent;

namespace DDDSample.Domain.Cargo
{
    /// <summary>
    /// 描述货运状态
    /// </summary>
    public class Delivery
    {
        private readonly DateTime? _eta;
        private readonly bool _misdirected;
        private readonly DateTime _calculatedAt;
        private readonly HandlingEvent.HandlingEvent _lastEvent;
        private readonly RoutingStatus _routingStatus;
        private readonly bool _isUnloadedAtDestination;
        private readonly TransportStatus _transportStatus;
        private readonly Location.Location _lastKnownLocation;
        private readonly HandlingActivity _nextExpectedActivity;

        public bool IsMisdirected
        {
            get { return _misdirected; }
        }

        public DateTime CalculatedAt
        {
            get { return _calculatedAt; }
        }

        public RoutingStatus RoutingStatus
        {
            get { return _routingStatus; }
        }

        public bool IsUnloadedAtDestination
        {
            get { return _isUnloadedAtDestination; }
        }

        public TransportStatus TransportStatus
        {
            get { return _transportStatus; }
        }

        public DateTime? EstimatedTimeOfArrival
        {
            get { return _eta; }
        }

        public Location.Location LastKnownLocation
        {
            get { return _lastKnownLocation; }
        }

        public HandlingActivity NextExpectedActivity
        {
            get { return _nextExpectedActivity; }
        }

        public static Delivery DerivedFrom(RouteSpecification specification, Itinerary itinerary, HandlingEvent.HandlingEvent lastHandlingEvent)
        {
            return new Delivery(lastHandlingEvent, itinerary, specification);
        }

        public Delivery UpdateOnRouting(RouteSpecification routeSpecification, Itinerary itinerary)
        {
            if (routeSpecification == null)
            {
                throw new ArgumentNullException("routeSpecification");
            }
            return new Delivery(_lastEvent, itinerary, routeSpecification);
        }

        protected Delivery()
        {
        }

        private Delivery(HandlingEvent.HandlingEvent lastHandlingEvent, Itinerary itinerary, RouteSpecification specification)
        {
            _calculatedAt = DateTime.Now;
            _lastEvent = lastHandlingEvent;

            _misdirected = CalculateMisdirectionStatus(itinerary);
            _routingStatus = CalculateRoutingStatus(itinerary, specification);
            _transportStatus = CalculateTransportStatus();
            _lastKnownLocation = CalculateLastKnownLocation();
            _eta = CalculateEta(itinerary);
            _nextExpectedActivity = CalculateNextExpectedActivity(specification, itinerary);
            _isUnloadedAtDestination = CalculateUnloadedAtDestination(specification);
        }

        private bool CalculateUnloadedAtDestination(RouteSpecification specification)
        {
            return LastEvent != null &&
                     LastEvent.EventType == HandlingEventType.Unload &&
                     specification.Destination == LastEvent.Location;
        }

        private DateTime? CalculateEta(Itinerary itinerary)
        {
            return OnTrack ? itinerary.FinalArrivalDate : null;
        }

        private Location.Location CalculateLastKnownLocation()
        {
            return LastEvent != null ? LastEvent.Location : null;
        }

        private TransportStatus CalculateTransportStatus()
        {
            if (LastEvent == null)
            {
                return TransportStatus.NotReceived;
            }

            switch (LastEvent.EventType)
            {
                case HandlingEventType.Load:
                    return TransportStatus.OnboardCarrier;
                case HandlingEventType.Unload:
                case HandlingEventType.Receive:
                case HandlingEventType.Customs:
                    return TransportStatus.InPort;
                case HandlingEventType.Claim:
                    return TransportStatus.Claimed;
                default:
                    return TransportStatus.Unknown;
            }
        }

        private HandlingActivity CalculateNextExpectedActivity(RouteSpecification routeSpecification, Itinerary itinerary)
        {
            if (!OnTrack)
            {
                return null;
            }

            if (LastEvent == null)
            {
                return new HandlingActivity(HandlingEventType.Receive, routeSpecification.Origin);
            }

            switch (LastEvent.EventType)
            {
                case HandlingEventType.Load:

                    Leg lastLeg = itinerary.Legs.FirstOrDefault(x => x.LoadLocation == LastEvent.Location);
                    return lastLeg != null ? new HandlingActivity(HandlingEventType.Unload, lastLeg.UnloadLocation) : null;

                case HandlingEventType.Unload:
                    IEnumerator<Leg> enumerator = itinerary.Legs.GetEnumerator();
                    while (enumerator.MoveNext())
                    {
                        if (enumerator.Current.UnloadLocation == LastEvent.Location)
                        {
                            Leg currentLeg = enumerator.Current;
                            return enumerator.MoveNext() ? new HandlingActivity(HandlingEventType.Load, enumerator.Current.LoadLocation) : new HandlingActivity(HandlingEventType.Claim, currentLeg.UnloadLocation);
                        }
                    }
                    return null;

                case HandlingEventType.Receive:
                    Leg firstLeg = itinerary.Legs.First();
                    return new HandlingActivity(HandlingEventType.Load, firstLeg.LoadLocation);
                default:
                    return null;
            }
        }

        private static RoutingStatus CalculateRoutingStatus(Itinerary itinerary, RouteSpecification specification)
        {
            if (itinerary == null)
            {
                return RoutingStatus.NotRouted;
            }
            return specification.IsSatisfiedBy(itinerary) ? RoutingStatus.Routed : RoutingStatus.Misrouted;
        }

        private bool CalculateMisdirectionStatus(Itinerary itinerary)
        {
            if (LastEvent == null)
            {
                return false;
            }
            return !itinerary.IsExpected(LastEvent);
        }

        private bool OnTrack
        {
            get { return RoutingStatus == RoutingStatus.Routed && !IsMisdirected; }
        }

        private HandlingEvent.HandlingEvent LastEvent
        {
            get { return _lastEvent; }
        }
    }
}